feat: per-workspace routing via X-Mcpmux-Workspace header + guided folder setup - #182
Merged
Conversation
Clients that don't report MCP `roots` reliably — notably Cursor, which multiplexes one MCP host across windows with `roots.listChanged=false` — could not be routed to the right workspace binding, so `@mux` saw another window's FeatureSet. `roots` is also deprecated (SEP-2577) and sessions are being removed (SEP-2575/2567), so connection-carried identity, not the client's roots, is the durable signal. Add an `X-Mcpmux-Workspace` request header whose value is the workspace root path. The OAuth middleware pins it into SessionRootsRegistry, where it shadows the client's probed roots in `get()`. Because the resolver, the on-demand probe skip, and the prompt-root derivation all read roots through `get()`, the header flows through Tier 1 unchanged — exact binding match, else Space default — and is authoritative over a stale or absent reported root. No new resolver tier, parameter, or DB migration; the header value is just a workspace root normalized like any other. Tests: registry pin/shadow/clear units; resolver integration cases proving a pinned root routes to its binding with no reported roots, overrides a conflicting reported root, and falls back to Space default when unmapped. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
Add a `gateway.auth_disabled` setting (default false — auth required) that, when on, lets inbound MCP clients connect without a Bearer token. This makes the upcoming one-click per-workspace install trivial: a client config needs only the URL + `X-Mcpmux-Workspace` header, no OAuth/access-key dance. The middleware is now lenient rather than all-or-nothing: a valid token is always honored when present, so flipping the setting never breaks an already-configured client. With auth disabled and no valid token, the connection is accepted as an anonymous client on the default Space; routing still prefers the workspace header → binding. The toggle lives in GatewayState (seeded from settings at startup, flipped live by set_gateway_auth_disabled so no restart is needed). Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
Add commands to register the gateway endpoint in a project-local MCP config inside a chosen folder, injecting `X-Mcpmux-Workspace: <folder path>` so the gateway routes the connection to that folder's workspace binding without relying on the client reporting MCP roots. - list_workspace_install_clients: the supported project-local clients (Cursor, Claude Code, VS Code/Copilot, opencode, Zed) with their config paths. Each client's config-shape differences (top-level key mcpServers / servers / mcp / context_servers, and the type field) live in one table. - generate_workspace_config_snippet: copy-paste full-file snippet per client. - install_workspace_mcp_config: create or extend each client's config, preserving other servers, backing up an existing file, creating parent dirs. Refuses to clobber a non-JSON (JSONC) file, reporting it instead. Global-only clients (Windsurf, Cline) and Claude Desktop (stdio, no static headers) are excluded — a per-workspace header needs project-local scope. Tested: entry shape per client, merge create/extend/replace, preserve siblings, reject JSONC/non-object, create-then-update-with-backup on disk. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
Frontend for the per-workspace MCP install: - WorkspaceInstallPanel: a "Connect apps to this folder" section in the Workspaces inspector. Lists the supported clients (Cursor, Claude Code, VS Code/Copilot, opencode, Zed), copies a per-client snippet, and writes the configs into the folder with this folder's path as the X-Mcpmux-Workspace header. Self-introductory: shows the system-wide auth state and offers an inline "Disable authentication" action, since turning it off makes the written config a pure URL + header. - Settings → Security: a "Disable authentication" toggle. - workspaceInstall API wrappers for the new commands. Tested (vitest): lists clients, installs the default-selected clients with the gateway /mcp url, flips auth inline, copies a snippet, and blocks install until the gateway is running. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
- Approval sheet and the Apps page's "Routing is workspace-driven" panel now point users at "Connect apps to this folder" when a client (e.g. Cursor) doesn't report the folder reliably. - Add a manual test guide for header routing, the per-workspace installer, and the disable-auth toggle, with log lines to look for. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
The "Connect apps to this folder" checklist defaulted to the common three every time. Persist the user's selection to localStorage (matching how the app stores other UI prefs) and restore it across folders and sessions, reconciled against the currently-supported clients so a dropped client can't leave a stale id (falling back to available defaults if pruning empties it). Test: deselect to one client + install, remount, and confirm the remembered selection is restored rather than the default. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
Lock in the requirement that folder-path/roots-reporter mapping keeps working alongside the X-Mcpmux-Workspace header, with the header taking priority. One session: with no pin it resolves via its reported root; pinning a header root for a different folder then overrides it. Guards against the pin ever becoming unconditional and breaking roots-reporting clients. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
Authentication is an application-wide control, so the per-workspace install
panel no longer flips it inline. The auth nudge now links to Settings →
Security (via navigateTo('settings')) where the user makes the deliberate
choice. Drops the inline setGatewayAuthDisabled call from the panel.
Test updated: the nudge renders an "Open Settings" action that navigates to
settings rather than toggling auth in place.
Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF
Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
Reframe the auth notice from "Apps will need an access key" to the actual one-time flow: you enable and authenticate the app once and then it connects, and the client authentication requirement can be disabled in Settings → Security for a no-key setup. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
Replace the multi-paragraph auth notice with a single concise line: "Enable and authenticate this app once to connect — or disable the requirement in Settings." Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
Reword the intro and remove the "(like Cursor)" example — keep it generic: "…so it routes here automatically — even apps that don't report the folder." Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
"Open Settings" from the install panel now lands on the Security section instead of the top of Settings. A `pendingSettingsSection` store signal is set to 'security' before navigating; SettingsPage scrolls that section into view and briefly flashes a ring around it, then clears the signal. Also tighten the Disable-authentication help text to two lines. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
New mapping (create path) is now a 3-step walkthrough; editing an existing
mapping still uses the inspector.
1. Folder — required; pick via dialog or a detected workspace.
2. Apps — optional; the install panel writes the per-workspace config
(X-Mcpmux-Workspace header) so apps route here.
3. Tools — defaults to the Space's Starter so Finish is one click; the
binding is created on Finish.
Abandoning before Finish is safe (the folder uses the default Starter until
mapped, and any installed config still points at it).
A "Set up a folder" card on Home opens the walkthrough via a
pendingWorkspaceNew store signal (WorkspacesPage launches it on arrival).
Tests: wizard walks the 3 steps and Finish creates the binding with the
folder path + chosen Space + default Starter; Back navigation. Updated the
three store-mocking suites for the new selectors.
Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF
Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
…s gateway - After the setup walkthrough's Finish, land on the new mapping's inspector (which shows its effective features) instead of just closing. The wizard no longer self-closes on create; the page transitions to the created entry. - Prove the gateway is truly authless when inbound auth is disabled: a new HTTP integration test drives the REAL oauth middleware and asserts a tokenless POST to /mcp is accepted (200, anonymous identity injected) when disabled, and rejected (401) when auth is required. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Clients that don't report MCP
rootsreliably — notably Cursor, which multiplexes one MCP host across windows withroots.listChanged=false— could not be routed to the right workspace binding, so@muxreturned another window's FeatureSet.rootsis also deprecated (SEP-2577) and sessions are being removed (SEP-2575/2567), so connection-carried identity, not the client's reported roots, is the durable signal.What
Gateway
X-Mcpmux-Workspaceheader routing. The header value is the workspace folder path; the OAuth middleware pins it intoSessionRootsRegistry, where it shadows the client's probed roots inget(). The resolver, probe-skip, and prompt-root all read throughget(), so the header flows through Tier 1 unchanged — authoritative over a stale/absent reported root. Coexists with roots-based mapping (header wins); no new tier, param, or DB migration.gateway.auth_disabled, default off). Middleware is now lenient: a valid token is always honored, but with auth off a tokenless client is accepted as an anonymous identity on the default Space. Seeded at startup, flipped live.Desktop
Testing
GatewayStatetoggle; config-writer merge/create/extend/backup/reject-JSONC; authless HTTP integration test driving the real middleware (tokenless/mcp→ 200 + anonymous when disabled, 401 when required).docs/manual/workspace-header-routing.md.Notes
roots-reporting clients (VS Code/Claude Code) are unaffected — the pin only applies when the header is present.https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF